#!/usr/bin/python
# coding:utf-8
# Copyright 2017, JAKA
# All rights reserved.

import configparser
import sys
import jkzuc
import os
import re
import traceback

s = jkzuc.stat()
oldIoAddrMap = [
    # cabinet modbus profinet
    {"di": [8, 136, 200], "do": [8, 136, 200]},  # cabv1
    {"di": [16, 144, 208], "do": [16, 144, 208]},  # cabv2
    {"di": [7, 135, 199], "do": [7, 135, 199]},  # minicab
]
configPath = jkzuc.jkroot_dir + "/configs/JAKA/"
usersettingFile = configPath + "usersettings.ini"
aliansnameFile = configPath + "aliasname.ini"


class SafetyIOConfigParser:
    """
    安全io配置处理器，处理不同控制器版本、scb版本间升降级的兼容问题。
    """

    def __init__(self, controller_version, scb_version):
        """
        Args:
            controller_version: int 型,小于等于0时认为无约束,两个版本不能同时小于等于0
            scb_version: int 型,小于等于0时认为无约束,两个版本不能同时小于等于0
        """
        self._controller_version = controller_version
        self._scb_version = scb_version
        if type(controller_version) != int or type(scb_version) != int:
            raise TypeError("controller_version AND scb_version MUST be int")
        if controller_version <= 0 and scb_version <= 0:
            raise ValueError("one of controller_version OR scb_version must be larger than 0")
        self._safety_di_map = [0, 0, 0, 0, 0, 0, 0, 0]
        self._safety_do_map = [0, 0, 0, 0, 0, 0, 0, 0]
        self._supported_function_di = [1, 2, 3, 4, 5]
        self._supported_function_do = [1, 2, 3, 4, 5]
        self.supproted_three_position_enable_drag_limit = 0
        self.get_supported_function()  # 初始化时，识别支持的功能码

    def _get_scb_supported_function(self):
        """
        根据初始化时的scb版本，获取该scb版本的可用的安全功能表
        Returns:
        该scb版本的可用的安全功能表
        """
        scb_map = {
            "max": {
                "di": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
                "do": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
            },
            262: {"di": [1, 2, 3, 4, 5], "do": [1, 2, 3, 4, 5, 7]},
            # 此版本do跳过了6实现了7，导致暂时没有太好的解决办法
            303: {
                "di": [
                    1,
                    2,
                    3,
                    4,
                    5,
                    6,
                ],
                "do": [1, 2, 3, 4, 5, 6, 7],
            },
        }  # todo:check version
        if self._scb_version <= 0:
            return scb_map["max"]
        elif self._scb_version < 262:
            return scb_map[262]
        elif self._scb_version < 303:
            return scb_map[303]
        else:
            return scb_map["max"]

    def _get_controller_supported_function(self):
        """
        根据初始化时的控制器版本，获取该控制器版本的可用的安全功能表
        Returns:
        该控制器版本的可用的安全功能表
        """
        controller_map = {
            "max": {
                "di": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
                "do": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
                "three_position_enable_drag_limit": 1,
            },
            1070100: {
                "di": [1, 2, 3, 4, 5],
                "do": [1, 2, 3, 4, 5, 7],
                "three_position_enable_drag_limit": 0,
            },  # 此之前的版本do跳过了6实现了7，暂时没有太好的解决办法
            1070108: {
                "di": [
                    1,
                    2,
                    3,
                    4,
                    5,
                    6,
                ],
                "do": [1, 2, 3, 4, 5, 6, 7],
                "three_position_enable_drag_limit": 1,
            },
        }  # todo:check version
        if self._controller_version <= 0:
            return controller_map["max"]
        elif self._controller_version < 1070100:
            return controller_map[1070100]
        elif self._controller_version < 1070108:
            return controller_map[1070108]
        else:
            return controller_map["max"]

    def get_supported_function(self):
        """
        根据控制器和scb的版本约束条件，获取支持的安全io，功能表,
        初始化时，自动执行一次
        Returns:
        由控制器和scb版本共同约束下的的安全功能表
        """
        scb_function_dict = self._get_scb_supported_function()  # 依照SCB版本号获取功能码
        controller_function_dict = self._get_controller_supported_function()  # 依照Controller版本号获取功能码
        self._supported_function_di = [i for i in scb_function_dict["di"] if i in controller_function_dict["di"]]  # 取scb&controller交集
        self._supported_function_do = [i for i in scb_function_dict["do"] if i in controller_function_dict["do"]]  # 取scb&controller交集
        self.supproted_three_position_enable_drag_limit = controller_function_dict["three_position_enable_drag_limit"]
        return {
            "di": self._supported_function_di,
            "do": self._supported_function_do,
            "three_position_enable_drag_limit": self.supproted_three_position_enable_drag_limit,
        }

    def set_safety_di_map(self, di_map):
        """
        输入现有安全di配置，输出根据版本条件约束过滤后的安全di配置
        Args:
            di_map: 现有安全di配置

        Returns:
        根据版本条件约束过滤后的安全di配置
        """
        return [i if i in self._supported_function_di else 1 for i in di_map]

    def set_safety_do_map(self, do_map):
        """
        输入现有安全do配置，输出根据版本条件约束过滤后的安全do配置
        Args:
            do_map: 现有安全di配置

        Returns:
        根据版本条件约束过滤后的安全do配置
        """
        return [i if i in self._supported_function_do else 1 for i in do_map]


class MyConfigParser(configparser.ConfigParser):

    def __init__(self, defaults=None):
        configparser.ConfigParser.__init__(self, defaults=defaults)

    def optionxform(self, optionstr):
        return optionstr


class ModuleCtx:

    def __init__(self, logger_creator):
        """
        logger_creator创建logger实例
        """
        self.logger_creator = logger_creator  # 用于输出日志

    def logger(self):
        return self.logger_creator()


def is_pn_Io(new, is_di):
    s.poll()
    index = oldIoAddrMap[0]["di"]
    cabIndex = s.cab_type - 1
    if is_di == True:
        index = oldIoAddrMap[cabIndex]["di"]
    else:
        index = oldIoAddrMap[cabIndex]["do"]
    if (new["type"] == 5) or (new["type"] == 0 and new["index"] >= index[1]):
        return True
    return False


def is_eip_io(new, isDI):
    if new["type"] == 6:
        return True
    return False


def get_old_io_index(new, isDI, rc_ver):
    old = new
    if int(rc_ver) <= 1059999:  # 1.5.1
        if is_pn_Io(new, isDI):
            old["index"] = -1
            old["type"] = -1
    if int(rc_ver) <= 1069999:  # 1.6.1
        if is_eip_io(new, isDI):
            old["index"] = -1
            old["type"] = -1
    return old


def get_rc_current_version():
    return jkzuc.controller_version


def get_rc_target_version(target_version_str):
    """
    以10进制，从输入参数，解析rc软件版本号
    Args:
        str target_version_str:

    Returns:
        int rc_version_num:

    """
    # ver_literal = re.split("\.|_", re.findall('JKZUC(\d+_\d+_\d+_\d+)', target_version_str)[0])
    ver_literal = re.split("\.|_", re.findall('JKZUC(\d+_\d+_\d+_)', target_version_str)[0])
    return int(ver_literal[2]) + int(ver_literal[1]) * 100 + int(ver_literal[0]) * 10000 # + int(ver_literal[0]) * 1000000


def get_scb_current_version():
    s.poll()
    scbVer = s.monitor_data
    verNum = 0
    if scbVer[0] and scbVer[1]:
        major = scbVer[0]
        minor = scbVer[1]
        majorStr = "%c%c" % ((major >> 8) & 0xFF, major & 0xFF)
        minorStr = "%c%c" % ((minor >> 8) & 0xFF, minor & 0xFF)
        verNum = int(majorStr, 10) * 100 + int(minorStr, 10)
    return verNum


def get_scb_target_version(target_version_str):
    """
    以10进制，从输入参数，解析scb软件版本号
    Args:
        str target_version_str:

    Returns:
        int scb_version_num:

    """
    return int(re.findall("SCB_APP_(\d+_\d+)", target_version_str)[0].replace("_", ""))


# 从1.7降级，因为进行了io重构，所以主要针对io类型和io索引进行修正
def handleFuncIO(config, rc_ver, module_ctx):
    module_ctx.logger().debug("handle funcio")
    diOptions = [
        "TASK_RUN_MAP_DI",
        "TASK_PAUSE_MAP_DI",
        "TASK_RESUME_MAP_DI",
        "TASK_STOP_MAP_DI",
        "POWER_ON_MAP_DI",
        "POWER_OFF_MAP_DI",
        "SERVO_ENABLE_MAP_DI",
        "SERVO_DISABLE_MAP_DI",
        "REDUCED_MODE_MAP_L1_DI",
        "REDUCED_MODE_MAP_L2_DI",
        "STOP_MODE_MAP_DI",
        "BACK_TO_INIPOS_MAP_DI",
        "CLEAR_ERROR_DI",
        "DRAG_MODE_ENABLE_DI",
        "DRAG_MODE_DISABLE_DI",
    ]
    doOptions = [
        "TASK_IDLE_DO",
        "TASK_PAUSED_DO",
        "TASK_RUNNING_DO",
        "STAT_FAULT_DO",
        "STAT_POWERON_DO",
        "STAT_ENABLED_DO",
        "STAT_MOTION_DO",
        "STAT_STATIC_DO",
        "STAT_BOOT_DO",
        "STAT_EMERGEMCY_STOP_DO",
        "STAT_REDUCED_MODE_DO",
        "STAT_SYS_PROTECTIVE_STOP_DO",
        "STAT_INITIAL_POSE_DO",
        "STAT_PERCENTAGE_MODE_LEVEL1_DO",
        "STAT_DRAG_MODE_DO",
        "STAT_COLLISION_MODE_DO",
        "STAT_MANUAL_MODE_DO",
        "STAT_AUTO_MODE_DO",
    ]
    for opt in diOptions:
        try:
            valStr = config.get("FUNCTIONIO", opt)
            new = {"type": -1, "index": -1}
            typeStr = re.search("\{(-?\d+):(-?\d+)\}", valStr).group(1)
            indexStr = re.search("\{(-?\d+):(-?\d+)\}", valStr).group(2)
            new["type"] = int(typeStr)
            new["index"] = int(indexStr)
            old = get_old_io_index(new, True, rc_ver)
            val = "{%d:%d}" % (old["type"], old["index"])
            module_ctx.logger().debug("trans %s: %s => %s" % (opt, valStr, val))
            config.set("FUNCTIONIO", opt, val)
        except Exception:
            module_ctx.logger().debug("get %s failed" % (opt))
            module_ctx.logger().debug(traceback.format_exc())
            return False

    for opt in doOptions:
        try:
            valStr = config.get("FUNCTIONIO", opt).strip("{}")
            news = []
            for doCfg in valStr.split(","):
                new = {"type": -1, "index": -1}
                new["type"] = int(re.search("(-?\d+):(-?\d+)", doCfg).group(1))
                new["index"] = int(re.search("(-?\d+):(-?\d+)", doCfg).group(2))
                old = get_old_io_index(new, False, rc_ver)
                news.append(old)
            val = "{"
            for item in news:
                val = val + "%d:%d," % (item["type"], item["index"])
            val = val.rstrip(",")
            val = val + "}"
            module_ctx.logger().debug("trans %s: %s => %s" % (opt, config.get("FUNCTIONIO", opt), val))
            config.set("FUNCTIONIO", opt, val)
        except Exception:
            module_ctx.logger().debug("get %s failed" % (opt))
            module_ctx.logger().debug(traceback.format_exc())
            return False
    return True


# 1.7.1安全DI和安全DO中均添加了新功能
# 所以在控制器降级到1.7.1以前，SCB降级到02_62以前均进行降级处理
def handle_safety_func_io(config, target_controller_ver, target_scb_ver, module_ctx):
    module_ctx.logger().debug("handle safety function IO")
    safety_io_parser = SafetyIOConfigParser(target_controller_ver, target_scb_ver)
    try:
        val_str = config.get("SAFETYFUNCTIONIO", "DI_CHN_FUNC_SETTINGS")
        old_config = [int(i) for i in re.findall("(-?\d+)", val_str)]  # 字符串转为int列表
        new = safety_io_parser.set_safety_di_map(old_config)
        val = "{%d,%d,%d,%d,%d,%d,%d,%d}" % (new[0], new[1], new[2], new[3], new[4], new[5], new[6], new[7])
        module_ctx.logger().debug("trans %s => %s" % (val_str, val))
        config.set("SAFETYFUNCTIONIO", "DI_CHN_FUNC_SETTINGS", val)
        if safety_io_parser.supproted_three_position_enable_drag_limit == 0:
            config.set("SAFETYFUNCTIONIO", "THREE_POSITION_ENABLING_DRAG_LIMIT", "0")
            module_ctx.logger().debug("trans THREE_POSITION_ENABLING_DRAG_LIMIT to 0")
        else:
            pass
        val_str = config.get("SAFETYFUNCTIONIO", "DO_CHN_FUNC_SETTINGS")
        for i in range(8):
            old_config = [int(i) for i in re.findall("(-?\d+)", val_str)]  # 字符串转为int列表
            new = safety_io_parser.set_safety_do_map(old_config)
        val = "{%d,%d,%d,%d,%d,%d,%d,%d}" % (new[0], new[1], new[2], new[3], new[4], new[5], new[6], new[7])
        module_ctx.logger().debug("trans %s => %s" % (val_str, val))
        config.set("SAFETYFUNCTIONIO", "DO_CHN_FUNC_SETTINGS", val)
    except Exception:
        module_ctx.logger().debug("handle_safety_func_io failed")
        module_ctx.logger().debug(traceback.format_exc())
        return False
    return True


# 1.7.1.8安全DI和安全DO中均添加了碰撞等级7
# 所以应产品需求,在控制器降级时，将碰撞灵敏度等级拉高
def handle_servo_param(config, target_controller_ver, target_scb_ver, module_ctx):
    module_ctx.logger().debug("handle servo_param")
    if target_controller_ver > 1070108:
        module_ctx.logger().debug("no need handle_servo_param")
        return True
    try:
        clsn_lv = int(config.get("SERVOPARAM", "CLSN_SENSITIVITY"))
        module_ctx.logger().debug(clsn_lv)
        if clsn_lv > 5:
            config.set("SERVOPARAM", "CLSN_SENSITIVITY", "5")
            module_ctx.logger().debug("trans CLSN_SENSITIVITY to 5")
        else:
            pass
    except Exception as err:
        module_ctx.logger().error(err)
        module_ctx.logger().error("handle_servo_param failed")
        module_ctx.logger().error(traceback.format_exc())
        return False
    return True


def usersettingHandle(target_rc_version, module_ctx):
    module_ctx.logger().debug("handle usersetting")
    config = MyConfigParser()
    config.read(usersettingFile)
    if False == handle_safety_func_io(config, target_rc_version, 0, module_ctx):  # 从1.7.1降级到1.7.0及以下版本均需进行此操作
        return -1
    if False == handle_servo_param(config, target_rc_version, 0, module_ctx):  # 从1.7.1.8降级均需进行此操作
        return -1
    if int(target_rc_version) <= 1069999:  # 1.7.1或1.7.0降级到1.6及以下版本进行此操作
        if False == handleFuncIO(config, target_rc_version, module_ctx):
            return -1
    with open(usersettingFile, "w") as fd:
        config.write(fd)
        os.fsync(fd)
    return 0


def aliasnameHandle(target_rc_version, module_ctx):
    def migrate_aliasname_downgrade(std_sec_name, mb_slave_sec_name, io_div_count):
        if not config.has_section(std_sec_name):
            return

        for optionKey, optionValue in config[mb_slave_sec_name].items():
            module_ctx.logger().debug("%s %s", optionKey, optionValue)
            if not config.has_section(std_sec_name):
                config.add_section(std_sec_name)
            config.set(std_sec_name, str(int(optionKey) + io_div_count), optionValue)
            module_ctx.logger().debug("remove %s:%s" % (optionKey, optionValue))
            config.remove_option(mb_slave_sec_name, optionKey.encode("utf-8"))

    if int(target_rc_version) <= 1069999:  # 只有降级到1.6以下版本进行此操作
        module_ctx.logger().debug("handle aliansname")
        try:
            s.poll()
            cabType = s.cab_type
            config = MyConfigParser()
            config.read(aliansnameFile)
            pnSectionsList = ["PN_DEV_DI", "PN_DEV_DO", "PN_DEV_AI", "PN_DEV_AO"]
            eipSectionsList = ["EIP_ADPT_DI", "EIP_ADPT_DO", "EIP_ADPT_AI", "EIP_ADPT_AO"]
            if int(target_rc_version) <= 1059999:  # 1.5.1
                for section in pnSectionsList:
                    for opt in config.options(section):
                        module_ctx.logger().debug("remove %s:%s" % (section, opt))
                        config.remove_option(section, opt.encode("utf-8"))
            if int(target_rc_version) <= 1069999:  # 1.6.1
                for section in eipSectionsList:
                    for opt in config.options(section):
                        module_ctx.logger().debug("remove %s:%s" % (section, opt))
                        config.remove_option(section, opt.encode("utf-8"))
            if cabType == 1:
                migrate_aliasname_downgrade("STD_DI", "MB_SLAVE_DI", 8)
                migrate_aliasname_downgrade("STD_DO", "MB_SLAVE_DO", 8)
                migrate_aliasname_downgrade("STD_AI", "MB_SLAVE_AI", 8)
                migrate_aliasname_downgrade("STD_AO", "MB_SLAVE_AO", 0)
            elif cabType == 2:
                migrate_aliasname_downgrade("STD_DI", "MB_SLAVE_DI", 16)
                migrate_aliasname_downgrade("STD_DO", "MB_SLAVE_DO", 16)
                migrate_aliasname_downgrade("STD_AI", "MB_SLAVE_AI", 2)
                migrate_aliasname_downgrade("STD_AO", "MB_SLAVE_AO", 2)
            elif cabType == 3:
                migrate_aliasname_downgrade("STD_DI", "MB_SLAVE_DI", 7)
                migrate_aliasname_downgrade("STD_DO", "MB_SLAVE_DO", 7)
                migrate_aliasname_downgrade("STD_AI", "MB_SLAVE_AI", 0)
                migrate_aliasname_downgrade("STD_AO", "MB_SLAVE_AO", 0)

            with open(aliansnameFile, "w") as fd:
                config.write(fd)
                os.fsync(fd)
            return 0
        except Exception:
            module_ctx.logger().error(traceback.format_exc())
            return -1
    return 0


# SCB 降级到 02_62以下进行此操作
def scb_downgrade_handle(str_target_version, module_ctx):
    module_ctx.logger().debug("handle usersetting")
    config = MyConfigParser()
    config.read(usersettingFile)
    if not handle_safety_func_io(config, 0, get_scb_target_version(str_target_version), module_ctx):
        return -1
    with open(usersettingFile, "w") as fd:
        config.write(fd)
        os.fsync(fd)
    return 0


# 控制器降级处理
def rc_downgrade_handle(str_target_rc_version, module_ctx):
    target_rc_version = get_rc_target_version(str_target_rc_version)
    usersettingHandle(target_rc_version, module_ctx)
    aliasnameHandle(target_rc_version, module_ctx)


# 传入升级包的全名
def run(str_target_version, module_ctx):
    """

    Args:
        str_target_version: A strings, name of target package

    Returns:
        0:downgrade success
        other: error
    """
    if "R" == str_target_version[0]:  # 伺服暂无降级处理
        module_ctx.logger().debug("%s is invalid" % str_target_version)
        return 0

    if "SCB" == str_target_version[0:3]:  # 判断进行SCB降级处理
        module_ctx.logger().debug("RC try downgrade from %s to %s" % (get_scb_current_version(), str_target_version))
        scb_downgrade_handle(str_target_version, module_ctx)
        return 0

    if "JKZUC" == str_target_version[0:5]:  # 控制器降级处理
        module_ctx.logger().debug("RC try downgrade from %s to %s" % (get_rc_current_version(), str_target_version))
        rc_downgrade_handle(str_target_version, module_ctx)
        return 0
    module_ctx.logger().debug("Unknown downgrade package name: %s " % str_target_version)
    return 0


if __name__ == "__main__":
    import logging

    # ver = sys.argv[1].encode("utf-8")

    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.DEBUG)

    logger = logging.getLogger(__name__)
    logger.addHandler(console_handler)
    logger.setLevel(logging.DEBUG)

    run("JKZUC1_7_1_99_alpha", ModuleCtx(lambda: logging.getLogger(__name__)))
    # run("JKZUC1_7_1_1_alpha", ModuleCtx(lambda : logging.getLogger(__name__)))
    # run("JKZUC1_5_13_27_alpha", ModuleCtx(lambda : logging.getLogger(__name__)))
    # run("SCB_APP_03_08_R", ModuleCtx(lambda : logging.getLogger(__name__)))
    # run("R1195_Zu710", ModuleCtx(lambda : logging.getLogger(__name__)))
    # usersettingHandle(1070100)
